/************************************************************************
Copyright (c) 2010, 2013, C.R. All rights reserved. 

E-mail: crtoyou@gmail.com; crtoyou@163.com

You can redistribute it and/or modify it under the terms of the
GNU Lesser General Public License as published by the
Free Software Foundation version 3 of the License.

This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. if not, write to the Free Software Foundation, Inc., 
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
************************************************************************/

#ifndef __CR_FILE_STREAM_H__
#define __CR_FILE_STREAM_H__

#include <crstream.h>

namespace cr {

class file_base 
{
private:
	file_base(const file_base&);
	file_base& operator = (const file_base&);

public:
	enum errcode {
		open_failed, 
		over_bound, 
		file_error 
	};

public:
	file_base()
		: m_pfile(NULL)
		, m_bound(0)
	{}

	~file_base()
	{
		ASSERT(!m_pfile);
	}

	inline void fbopen(const TCHAR* path, const TCHAR* mode)
	{
		if (0 != _tfopen_s(&m_pfile, path, mode))
			throw exception<file_base>(open_failed);
		m_bound = MAXLONGLONG;
	}

	inline void fbclose()
	{
		fclose(m_pfile);
		m_pfile = NULL;
	}

	inline void fblimit(__int64 lsize = MAXLONGLONG, __int64 offset = 0)
	{
		ASSERT(lsize >= 0 && offset >= 0);
		m_bound = (uint64)offset + lsize;
		_fseeki64(m_pfile, offset, SEEK_SET);
	}

	inline __int64 fbsize()
	{
		__int64 pos = _ftelli64(m_pfile);
		_fseeki64(m_pfile, 0, SEEK_END);
		__int64 res = _ftelli64(m_pfile);
		_fseeki64(m_pfile, pos, SEEK_SET);
		return res;
	}

	inline __int64 fbspace() const
	{
		ASSERT(m_bound <= MAXLONGLONG);
		return m_bound - fbtell();
	}

public:
	inline __int64 fbtell() const { return _ftelli64(m_pfile); }
	inline void fbseek(__int64 offset, int mode) { _fseeki64(m_pfile, offset, mode); }
	inline bool fbeof() const { return !!feof(m_pfile); }
	inline size_t fbread(void* buf, size_t len) { return fread(buf, 1, len, m_pfile); }
	inline size_t fbwrite(void* buf, size_t len) { return fwrite(buf, 1, len, m_pfile); }

protected:
	byte m_buf[BUFSIZ];
	uint64 m_bound;
private:
	FILE* m_pfile;

};

class file_istream : public istream_base, public file_base
{
public:
	inline void open(const TCHAR* path)
	{
		fbopen(path, _T("rb"));
		istream_base::bind(m_buf, BUFSIZ);
		fbread(m_buf, BUFSIZ);
	}

	inline void close()
	{
		TRACE(_T("====== file_istream::close ======\n"));
		//don't flush before close on read mode
		fbclose();
	}

	inline void reset(__int64 lsize = MAXLONGLONG, __int64 offset = 0)
	{
		fblimit(lsize, offset);
		if (lsize > BUFSIZ)
			lsize = BUFSIZ;
		istream_base::bind(m_buf, (size_t)lsize);
		fbread(m_buf, (size_t)lsize);
		//flush(); //flush before any byte to be read
	}

public:
	virtual bool flush()
	{
		TRACE(_T("====== file_istream::flush ======\n"));
		size_t len;
		__int64 space = fbspace();
		if (space > BUFSIZ)
			len = BUFSIZ;
		else if (space >= 0)
			len = (size_t)space;
		else throw exception<file_base>(over_bound);
		len = fbread(m_buf, len);
		istream_base::bind(m_buf, len);
		return len > 0;
	}

};

class file_ostream : public ostream_base, public file_base
{
public:
	inline void open(const TCHAR* path)
	{
		fbopen(path, _T("wb"));
		ostream_base::bind(m_buf, BUFSIZ);
		//don't flush before write
	}

	inline void close()
	{
		TRACE(_T("====== file_ostream::close ======\n"));
		if (isvalid()) {
			size_t len = occupy();
			if (len > 0) {
				fbwrite(m_buf, len);
			}
		}
		fbclose();
	}

	inline void reset(__int64 lsize = MAXLONGLONG, __int64 offset = 0)
	{
		file_base::fblimit(lsize, offset);
		if (lsize > BUFSIZ)
			lsize = BUFSIZ;
		ostream_base::bind(m_buf, (size_t)lsize);
		//don't flush before write
	}

public:
	virtual bool flush()
	{
		TRACE(_T("====== file_ostream::flush ======\n"));
		if (!isvalid())
			throw exception<file_base>(file_error);
		size_t len = occupy();
		if (len > 0 && len != fbwrite(m_buf, len)) 
			return false;
		__int64 space = fbspace();
		if (space > BUFSIZ)
			len = BUFSIZ;
		else if (space >= 0) 
			len = (size_t)space;
		else throw exception<file_base>(over_bound);
		ostream_base::bind(m_buf, len);
		return len > 0;
	}

};

class polyfile_base
{
};

class polyfile_istream
{

};

class polyfile_ostream
{

};

}//namespace

#endif //__CR_FILE_STREAM_H__